#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <signal.h>
#include <stdlib.h>
#include "mem.h"
#include "thread.h"
#include "redmapobject.h"
#include "rednullobject.h"

static RedTypeObject ThreadType =
{
	RedType_INIT("_thread", RedThread, RedType_Object)
};

static RedThread main_thread =
{
	RedObject_HEAD_INIT(ThreadType)

	.args			= NULL,
	.retval			= NULL,
	.function		= NULL,

	.daemon			= 0,
	.throwing		= 0,
	.exception		= NULL,

	.cond			= PTHREAD_COND_INITIALIZER,
	.mutex			= PTHREAD_MUTEX_INITIALIZER,
};

static RedObject *threads = NULL;

/* generic */

// FIXME: reimplement gettid() using a more portable way
static inline int64_t gettid(void)
{
#ifndef __APPLE__
	return (int64_t)pthread_self();
#else
	int64_t tid;
	pthread_threadid_np(pthread_self(), (uint64_t *)&tid);
	return tid;
#endif
}

void Red_FreeThreadManager(void)
{
	RedPoolNode *node = RedMap_PoolList(threads);

	while (node->lnext != node)
	{
		RedThread *thread = (RedThread *)node->lnext->value;

		if (thread == &main_thread)
		{
			/* main thread, remove directly */
			RedPool_DelInt(threads, thread->tid);
		}
		else if (!(thread->daemon))
		{
			/* not a daemon thread, wait it gentally */
			Red_REF(thread);
			RedThread_Join(thread);

			/* release resources */
			Red_UNREF(thread->args);
			Red_UNREF(thread->retval);
			Red_UNREF(thread->exception);
			Red_UNREF(thread);
		}
		else
		{
			/* daemon thread, kill it */
			pthread_kill(thread->thread, SIGKILL);
			pthread_attr_destroy(&(thread->attr));
			pthread_cond_destroy(&(thread->cond));
			pthread_mutex_destroy(&(thread->mutex));

			/* remove from thread map */
			RedPool_DelInt(threads, thread->tid);

			/* release resources */
			Red_UNREF(thread->args);
			Red_UNREF(thread->retval);
			Red_UNREF(thread->exception);
			Red_UNREF(thread);
		}
	}

	Red_UNREF(threads);
	RedType_Cleanup(&ThreadType);
}

char Red_InitThreadManager(void)
{
	if (!(threads = RedPool_Create()) ||
		!RedType_Prepare(&ThreadType))
		return 0;

	main_thread.tid = gettid();
	main_thread.thread = pthread_self();

	RedType_Init(&ThreadType);
	RedPool_PutInt(threads, main_thread.tid, (RedObject *)(&main_thread));
	return 1;
}

/* spin lock */

void RedSpin_Acquire(RedSpin *self)
{
	while (__sync_lock_test_and_set(self, 1)) while (*self);
}

void RedSpin_Release(RedSpin *self)
{
	__sync_lock_release(self);
}

/* mutex */

void RedMutex_Free(RedMutex *self)
{
	RedMutex_Release(self);
	pthread_mutex_destroy(&(self->mutex));
	pthread_mutexattr_destroy(&(self->attrs));
}

void RedMutex_Init(RedMutex *self)
{
	pthread_mutexattr_init(&(self->attrs));
	pthread_mutexattr_settype(&(self->attrs), PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&(self->mutex), &(self->attrs));
}

void RedMutex_Acquire(RedMutex *self)
{
	pthread_mutex_lock(&(self->mutex));
}

void RedMutex_Release(RedMutex *self)
{
	pthread_mutex_unlock(&(self->mutex));
}

/* semaphore */

void RedSemaphore_Free(RedSemaphore *self)
{
#ifdef __CYGWIN__
	CloseHandle(self->semaphore);
#else
	sem_close(self->semaphore);
	sem_unlink(self->name);
#endif
}

void RedSemaphore_Init(RedSemaphore *self, int count)
{
#ifdef __CYGWIN__
	if (!(self->semaphore = CreateSemaphore(NULL, count, 2147483647, NULL)))
	{
		fprintf(stderr, "*** FATAL: unable to create more semaphore(error: %u)\n", GetLastError());
		abort();
	}
#else
	memset(&(self->name), 0, RED_SEM_NAMES);
	snprintf(self->name, RED_SEM_NAMES, "RedScript-Sem-%ld", (intptr_t)self);

	if ((self->semaphore = sem_open(self->name, O_CREAT, 0644, count)) == SEM_FAILED)
	{
		fprintf(stderr, "*** FATAL: unable to create more semaphore(%s)(%s)\n", strerror(errno), self->name);
		abort();
	}
#endif
}

void RedSemaphore_Wait(RedSemaphore *self)
{
#ifdef __CYGWIN__
	WaitForSingleObject(self->semaphore, INFINITE);
#else
	sem_wait(self->semaphore);
#endif
}

void RedSemaphore_Signal(RedSemaphore *self)
{
#ifdef __CYGWIN__
	ReleaseSemaphore(self->semaphore, 1, NULL);
#else
	sem_post(self->semaphore);
#endif
}


/* thread */

static void *thread_proc(void *context)
{
	RedThread *thread = (RedThread *)context;

	/* add this thread to thread map */
	pthread_mutex_lock(&(thread->mutex));
	thread->tid = gettid();
	RedPool_PutInt(threads, thread->tid, (RedObject *)thread);
	pthread_mutex_unlock(&(thread->mutex));
	pthread_cond_signal(&(thread->cond));

	/* execute the function, store it's return into thread object */
	RedObject *result = thread->retval = thread->function(thread->args);

	/* remove from thread map after execution */
	RedPool_DelInt(threads, thread->tid);
	pthread_attr_destroy(&(thread->attr));
	pthread_cond_destroy(&(thread->cond));
	pthread_mutex_destroy(&(thread->mutex));

	/* throw the exception out */
	if (!result && thread->exception)
	{
		Red_REF(Red_Null);
		result = Red_Null;

		/* system exit should not be printed */
		if (!RedType_IsInstance(thread->exception, &RedExc_SystemExit))
			RedObject_Print(thread->exception);
	}

	/* release resources */
	Red_UNREF(thread->args);
	Red_UNREF(thread->exception);
	Red_UNREF(thread);
	return result;
}

RedThread *RedThread_Self(void)
{
	int64_t tid = gettid();
	RedThread *thread = (RedThread *)RedPool_GetInt(threads, tid);

	/* thread not found, this thread is not managed by RedThreadManager */
	if (!thread)
		return NULL;

	/* we don't want it's ref-count to be increased */
	ATOMIC_DEC(thread->ob_head.ob_ref);
	return thread;
}

RedObject *RedThread_Join(RedThread *thread)
{
	/* cannot join daemon or main thread, or it will deadlock */
	if (thread->daemon || thread == &main_thread)
		return NULL;

	RedObject *result;
	return pthread_join(thread->thread, (void **)(&result)) ? NULL : result;
}

RedThread *RedThread_Create(RedThreadFunction func, RedObject *args, char daemon)
{
	RedThread *result = (RedThread *)RedObject_Create(&ThreadType, NULL);

	if (!result)
		return NULL;

	Red_REF(args);
	result->args = args;
	result->retval = NULL;
	result->daemon = daemon;
	result->throwing = 0;
	result->function = func;
	result->exception = NULL;

	pthread_attr_init(&(result->attr));
	pthread_cond_init(&(result->cond), NULL);
	pthread_mutex_init(&(result->mutex), NULL);
	pthread_attr_setdetachstate(&(result->attr), daemon ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE);
	pthread_mutex_lock(&(result->mutex));
	pthread_create(&(result->thread), &(result->attr), thread_proc, result);
	pthread_cond_wait(&(result->cond), &(result->mutex));
	pthread_mutex_unlock(&(result->mutex));
	return result;
}
